#!/usr/bin/env bash

include "objectv5.sh"
include "output.sh"
include "pythonv2.sh"
include "regex.sh"
include "test.sh"

ObjectV5::DefineClass YamlV2 "
    --cargs=yamlpath
    --string=yamlpath
    --string=python_server,0
"

function YamlV2::YamlV2()
{
    [ $# == 0 ] && assert_fail "this"
    local this="$1"; shift
    local yamlpath="$(YamlV2::yamlpath "$this")"
    if ! [ -f "$yamlpath" ]; then
        loge "YamlV2 $this 对象创建失败，因为'$yamlpath'不是文件"
        return 1
    fi

    local python_server
    if ! PythonV2::New python_server; then
        loge "YamlV2 $1 对象创建失败，因为python_server对象创建失败"
        return 1
    fi
    YamlV2::python_server "$this" "$python_server"

    if ! PythonV2::StartServer python_server; then
        loge "YamlV2 $1 对象创建失败，因为python_server对象启动失败"
        return 1
    fi

    local code="
        import yaml
        with open('$1', 'r') as f:
            data = yaml.load(f.read(), Loader=yaml.FullLoader)
        response(True)
    "
    local response="$(PythonV2::Exec python_server "$(print_source 8 code)")"
    case "$response" in
        "True") return 0 ;;
        "No module named 'yaml'")
            loge "YamlV2模块依赖python的组件yaml，可以通过下列命令进行安装"
            loge "  pip install pyyaml -i https://pypi.tuna.tsinghua.edu.cn/simple" ;;
        "No mod"*) loge "YamlV2模块依赖python的组件: '$response'" ;;
        "[Errno"*) loge "打开文件'$yamlpath'失败: '$response'" ;;
        *) loge "解析Yaml文件'$yamlpath'失败: '$response'" ;;
    esac
    return 1
}

function YamlV2::~YamlV2()
{
    [ $# == 0 ] && assert_fail "this"
    local this="$1"; shift
    local python_server="$(YamlV2::python_server "$this")"
    PythonV2::Del python_server
    return
}

function YamlV2::___GetKeys()
{
    local key=""
    local i
    for (( i = 1; i <= $#; i++ )); do
        if [[ "${!i}" =~ (^$regex_dec_integer$) ]]; then
            # 数字
            key+="[${!i}]"
        else
            # 字符串
            key+="['${!i}']"
        fi
    done
    echo "$key"
    return
}

function YamlV2::IsExists()
{
    [ $# == 0 ] && assert_fail "this"
    local this="$1"; shift
    local python_server="$(YamlV2::python_server "$this")"
    local keys="$(YamlV2::___GetKeys "$@")"
    local code="
        try:
            response(data$keys == data$keys)
        except:
            response(False)
    "
    [ "$(PythonV2::Exec python_server "$(print_source 8 code)")" == "True" ]
    return
}

function YamlV2::_InstanceOf()
{
    [ $# == 0 ] && assert_fail "this"
    local this="$1"; shift
    local python_server="$(YamlV2::python_server "$this")"
    local type="$1"; shift

    if ! YamlV2::IsExists "$this" "$@"; then
        assert_fail "元素$@不存在" || return
    fi

    local code="
        response(type(data$(YamlV2::___GetKeys "$@")) == $type)
    "
    [ "$(PythonV2::Exec python_server "$(print_source 8 code)")" == "True" ]
    return
}

function YamlV2::IsArray()
{
    [ $# == 0 ] && assert_fail "this"
    local this="$1"; shift
    YamlV2::_InstanceOf "$this" list "$@"
    return
}

function YamlV2::IsObject()
{
    [ $# == 0 ] && assert_fail "this"
    local this="$1"; shift
    YamlV2::_InstanceOf "$this" dict "$@"
    return
}

function YamlV2::IsString()
{
    [ $# == 0 ] && assert_fail "this"
    local this="$1"; shift
    YamlV2::_InstanceOf "$this" str "$@"
    return
}

function YamlV2::IsBool()
{
    [ $# == 0 ] && assert_fail "this"
    local this="$1"; shift
    YamlV2::_InstanceOf "$this" bool "$@"
    return
}

function YamlV2::IsInteger()
{
    [ $# == 0 ] && assert_fail "this"
    local this="$1"; shift
    YamlV2::_InstanceOf "$this" int "$@"
    return
}

function YamlV2::IsFloat()
{
    [ $# == 0 ] && assert_fail "this"
    local this="$1"; shift
    YamlV2::_InstanceOf "$this" float "$@"
    return
}

function YamlV2::IsNull()
{
    [ $# == 0 ] && assert_fail "this"
    local this="$1"; shift
    local python_server="$(YamlV2::python_server "$this")"

    local code="
        response(data$(YamlV2::___GetKeys "$@") == None)
    "
    [ "$(PythonV2::Exec python_server "$(print_source 8 code)")" == "True" ]
    return
}

# 获取yaml对象的所有键, 可以用 for in 来遍历
# for key in $(YamlV2::GetKeys); do echo $key; done
function YamlV2::GetKeys()
{
    [ $# == 0 ] && assert_fail "this"
    local this="$1"; shift
    local python_server="$(YamlV2::python_server "$this")"

    if ! YamlV2::IsObject "$this" "$@"; then
        assert_fail "元素$@不是对象" || return
    fi

    local code="
        response(' '.join(data$(YamlV2::___GetKeys "$@").keys()))
    "
    PythonV2::Exec python_server "$(print_source 8 code)"
    return
}

function YamlV2::GetVal()
{
    [ $# == 0 ] && assert_fail "this"
    local this="$1"; shift
    local python_server="$(YamlV2::python_server "$this")"

    local code="
        response(data$(YamlV2::___GetKeys "$@"))
    "
    PythonV2::Exec python_server "$(print_source 8 code)"
    return
}

function YamlV2::GetLen()
{
    [ $# == 0 ] && assert_fail "this"
    local this="$1"; shift
    local python_server="$(YamlV2::python_server "$this")"

    local code="
        response(len(data$(YamlV2::___GetKeys "$@")))
    "
    PythonV2::Exec python_server "$(print_source 8 code)"
    return
}

function YamlV2::Test1()
{
    local testyaml="$(test::get_temp_path yamlv2.test1)"
    function YamlV2::Test1::PrepareTestText()
    {
        local yaml_source="
            repo_dir: \$HOME/repo/srepos
            repos:
              https://gitee.com/karl1864:
                private_data:
                  action:
                    - ln ssh/config \$HOME/.ssh/config
                config_crx:
                  action:
                    - ln . \$HOME/crx
                    - 123
                    - 1.23
                    - 1e9
                    - 1230000000.0
                    - 077
                    - 0xabc
                    - 0b1010_0111_0100_1010_1110
                    - TRUE
                    - FALSE
                    - ~
                    - a
                      b
                      c
                    -
                      a: 1
                      b: 2
                      c: ~
        "
        cat <<< "$(print_source 8 yaml_source)" > "$testyaml"
    }

    test::should_return_false YamlV2::New yaml "$testyaml"
                              YamlV2::Test1::PrepareTestText
    test::should_return_true  YamlV2::New yaml "$testyaml"

    test::should_return_true  YamlV2::IsExists yaml repo_dir

    test::run_cmd             YamlV2::GetVal yaml repo_dir
    test::should_be           '$HOME/repo/srepos'

    test::should_return_false YamlV2::IsExists yaml repo
    test::should_return_true  YamlV2::IsExists yaml repos
    test::should_return_true  YamlV2::IsObject yaml repos
    test::should_return_true  YamlV2::IsExists yaml repos https://gitee.com/karl1864
    test::should_return_true  YamlV2::IsObject yaml repos https://gitee.com/karl1864
    test::should_return_true  YamlV2::IsExists yaml repos https://gitee.com/karl1864 private_data
    test::should_return_true  YamlV2::IsObject yaml repos https://gitee.com/karl1864 private_data
    test::should_return_true  YamlV2::IsExists yaml repos https://gitee.com/karl1864 private_data action
    test::should_return_true  YamlV2::IsArray  yaml repos https://gitee.com/karl1864 private_data action

    local keys=(repos https://gitee.com/karl1864 private_data action)
    test::run_cmd             YamlV2::GetLen yaml ${keys[*]}
    test::should_be           "1"

    test::run_cmd             YamlV2::GetVal yaml ${keys[*]} 0
    test::should_be           "ln ssh/config \$HOME/.ssh/config"

    keys=(repos https://gitee.com/karl1864 config_crx action)
    test::run_cmd             YamlV2::GetLen yaml ${keys[*]}
    test::should_be           "13"

    test::should_return_true  YamlV2::IsString yaml ${keys[*]} 0
    test::run_cmd             YamlV2::GetVal yaml ${keys[*]} 0
    test::should_be           "ln . \$HOME/crx"

    test::should_return_true  YamlV2::IsInteger yaml ${keys[*]} 1
    test::run_cmd             YamlV2::GetVal yaml ${keys[*]} 1
    test::should_be           "123"

    test::should_return_true  YamlV2::IsFloat yaml ${keys[*]} 2
    test::run_cmd             YamlV2::GetVal yaml ${keys[*]} 2
    test::should_be           "1.23"

    test::should_return_true  YamlV2::IsString yaml ${keys[*]} 3
    test::run_cmd             YamlV2::GetVal yaml ${keys[*]} 3
    test::should_be           "1e9"

    test::should_return_true  YamlV2::IsFloat yaml ${keys[*]} 4
    test::run_cmd             YamlV2::GetVal yaml ${keys[*]} 4
    test::should_be           "1230000000.0"

    test::should_return_true  YamlV2::IsInteger yaml ${keys[*]} 5
    test::run_cmd             YamlV2::GetVal yaml ${keys[*]} 5
    test::should_be           "$((077))"

    test::should_return_true  YamlV2::IsInteger yaml ${keys[*]} 6
    test::run_cmd             YamlV2::GetVal yaml ${keys[*]} 6
    test::should_be           "$((0xabc))"

    test::should_return_true  YamlV2::IsInteger yaml ${keys[*]} 7
    test::run_cmd             YamlV2::GetVal yaml ${keys[*]} 7
    test::should_be           "$((2#10100111010010101110))"

    test::should_return_true  YamlV2::IsBool yaml ${keys[*]} 8
    test::run_cmd             YamlV2::GetVal yaml ${keys[*]} 8
    test::should_be           "True"

    test::should_return_true  YamlV2::IsBool yaml ${keys[*]} 9
    test::run_cmd             YamlV2::GetVal yaml ${keys[*]} 9
    test::should_be           "False"

    test::should_return_true  YamlV2::IsNull yaml ${keys[*]} 10
    test::run_cmd             YamlV2::GetVal yaml ${keys[*]} 10
    test::should_be           "None"

    test::should_return_true  YamlV2::IsString yaml ${keys[*]} 11
    test::run_cmd             YamlV2::GetVal yaml ${keys[*]} 11
    test::should_be           "a b c"

    test::should_return_true  YamlV2::IsObject yaml ${keys[*]} 12
    test::run_cmd             YamlV2::GetVal yaml ${keys[*]} 12
    test::should_be           "{'a': 1, 'b': 2, 'c': None}"

    test::should_return_true  YamlV2::IsInteger yaml ${keys[*]} 12 a
    test::run_cmd             YamlV2::GetVal yaml ${keys[*]} 12 a
    test::should_be           "1"

    test::should_return_true  YamlV2::IsInteger yaml ${keys[*]} 12 b
    test::run_cmd             YamlV2::GetVal yaml ${keys[*]} 12 b
    test::should_be           "2"

    test::should_return_true  YamlV2::IsNull yaml ${keys[*]} 12 c
    test::run_cmd             YamlV2::GetVal yaml ${keys[*]} 12 c
    test::should_be           "None"

    test::run_cmd             YamlV2::GetKeys yaml ${keys[*]} 12
    test::should_be           "a b c"

                              YamlV2::Del yaml
                              rm -f "$testyaml"
    return
}
test::register_test YamlV2::Test1
